package com.example.eazytransfer.model;

import javafx.application.Platform;
import org.apache.commons.io.FileUtils;

import java.io.*;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TCPUtil {

    private ServerSocket serverSocket;
    private Socket socket;
    private ObjectInputStream receiveInputStream;
    private ObjectOutputStream sendOutputStream;
    private ExecutorService executor = Executors.newSingleThreadExecutor();
    private BufferedOutputStream sendBufferedOutputStream;
    private BufferedInputStream receiveBufferedInputStream;

    private TCPUtil() {
    }

    private static final class TCPUtilHolder {
        static final TCPUtil tcpUtil = new TCPUtil();
    }

    public static TCPUtil getInstance() {
        return TCPUtilHolder.tcpUtil;
    }


    public void connectService() {
        try {
            socket = new Socket(ViewModel.getInstance().connectIP.get(), Integer.parseInt(ViewModel.getInstance().connectPort.get()));
            OutputStream outputStream = socket.getOutputStream();
            sendOutputStream = new ObjectOutputStream(outputStream);
            sendBufferedOutputStream = new BufferedOutputStream(outputStream);
            Platform.runLater(() -> ViewModel.getInstance().isConnected.set(true));
            new ReceiveThread().start();
        } catch (IOException e) {
            Platform.runLater(() -> ViewModel.getInstance().isConnected.set(false));
            Log.write(e.getMessage());
            throw new RuntimeException(e);
        }
    }

    public void startService() {
        try {
            String port = ViewModel.getInstance().servicePort.get();
            int port1 = port == null || port.equals("") ? 5000 : Integer.parseInt(port);
            int port2 = Math.max(port1, 5000);
            int port3 = Math.min(port2, 9999);
            Platform.runLater(() -> ViewModel.getInstance().servicePort.set(String.valueOf(port3)));
            serverSocket = new ServerSocket(port3);
            new Thread(() -> {
                try {
                    socket = serverSocket.accept();
                    OutputStream outputStream = socket.getOutputStream();
                    sendOutputStream = new ObjectOutputStream(outputStream);
                    sendBufferedOutputStream = new BufferedOutputStream(outputStream);
                    Platform.runLater(() -> ViewModel.getInstance().isConnected.set(true));
                    new ReceiveThread().start();
                } catch (IOException e) {
                    Log.write(e.getMessage());
                    throw new RuntimeException(e);
                }
            }).start();
        } catch (IOException e) {
            Log.write(e.getMessage());
            Platform.runLater(() -> ViewModel.getInstance().isServerStart.set(false));
            e.printStackTrace();
        }
    }

    public synchronized void close() {
        try {
            if (sendOutputStream != null)
                sendOutputStream.close();
            if (receiveBufferedInputStream != null)
                receiveBufferedInputStream.close();
            if (sendBufferedOutputStream != null)
                sendBufferedOutputStream.close();
            if (receiveInputStream != null)
                receiveInputStream.close();
            if (socket != null)
                socket.close();
            if (serverSocket != null)
                serverSocket.close();
        } catch (IOException e) {
            Log.write(e.getMessage());
            throw new RuntimeException(e);
        } finally {
            Platform.runLater(() -> {
                ViewModel.getInstance().isServerStart.set(false);
                ViewModel.getInstance().isConnected.set(false);
            });
            executor.shutdown();
        }
    }

    public synchronized void sendMsg(String msg) {
        executor.execute(() -> {
            try {
                sendOutputStream.writeChars(msg);
                System.out.println("send:" + msg);
                Thread.sleep(500);
            } catch (IOException | InterruptedException e) {
                Log.write(e.getMessage());
                throw new RuntimeException(e);
            }

        });
    }

    public synchronized void sendFile(String filePath) {
        executor.execute(() -> {
            try {
                Data data = new Data();
                if ("fileSuccess".equals(filePath)) {
                    data.fileName = filePath;
                    data.fileSize = 0;
                    sendOutputStream.writeObject(data);
                } else {
                    Platform.runLater(() -> ViewModel.getInstance().filePathOnSending.set(filePath));
                    FileInputStream fileInputStream = new FileInputStream(filePath);
                    //发送文件名、文件大小

                    File file = new File(filePath);
                    data.fileName = file.getName();
                    data.fileSize = FileUtils.sizeOf(file);
                    sendOutputStream.writeObject(data);
                    //开始传输
                    byte[] bytes = new byte[1024];
                    BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
                    int len;
                    int l = 0;
                    while ((len = bufferedInputStream.read(bytes)) != -1) {
                        sendBufferedOutputStream.write(bytes, 0, len);
                        l += len;
                        int finalL = l;
                        Platform.runLater(() -> {
                            ViewModel.getInstance().sendingProgress.set((double) finalL / data.fileSize);
                            String p = String.valueOf(((double) finalL / data.fileSize) * 100);
                            if (p.length() > 5)
                                p = p.substring(0, 5);
                            ViewModel.getInstance().sendingProgressPercent.set(p + "%");
                        });
                    }
                    //传输完成
                    sendBufferedOutputStream.flush();
                    bufferedInputStream.close();
                    //发送完成移除item
                    Platform.runLater(() -> {
                        ViewModel.getInstance().filePathListForSend.remove(filePath);
                        ViewModel.getInstance().sendingProgress.set(0);
                        ViewModel.getInstance().sendingProgressPercent.set("0%");
                        Platform.runLater(() -> ViewModel.getInstance().filePathOnSending.set("无"));
                    });
                    synchronized (TCPUtil.class) {
                        TCPUtil.class.wait();
                    }
                }
            } catch (IOException e) {
                Log.write(e.getMessage());
                throw new RuntimeException(e);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
    }

    private class ReceiveThread extends Thread {
        @Override
        public void run() {
            try {
                InputStream inputStream = socket.getInputStream();
                receiveInputStream = new ObjectInputStream(inputStream);
                receiveBufferedInputStream = new BufferedInputStream(inputStream);
                byte[] bytes = new byte[1024];
                int len = -1;
                BufferedOutputStream bufferedOutputStream = null;
                File file = null;
                long fileSize = 0, l = 0;
                while (true) {
                    //读取data
                    Data data = (Data) receiveInputStream.readObject();
                    if ("fileSuccess".equals(data.fileName) && data.fileSize == 0) {
                        synchronized (TCPUtil.class) {
                            TCPUtil.class.notifyAll();
                        }

                    } else {
                        fileSize = data.fileSize;
                        l = 0;
                        file = new File(ViewModel.getInstance().dirForReceive.get(), data.fileName);
                        if (!file.exists()) {
                            file.createNewFile();
                        }
                        File finalFile1 = file;
                        Platform.runLater(() -> ViewModel.getInstance().filePathOnReceiving.set(finalFile1.getAbsolutePath()));
                        bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(file));
                        //开始接收文件
                        while ((len = receiveBufferedInputStream.read(bytes)) != -1) {
                            bufferedOutputStream.write(bytes, 0, len);
                            l += len;
                            long finalL = l;
                            long finalFileSize = fileSize;
                            Platform.runLater(() -> {
                                ViewModel.getInstance().receivingProgress.set((double) finalL / finalFileSize);
                                String p = String.valueOf(((double) finalL / finalFileSize) * 100);
                                if (p.length() > 5) p = p.substring(0, 5);
                                ViewModel.getInstance().receivingProgressPercent.set(p + "%");
                            });
                            System.out.println(len + " " + l + "/" + fileSize);
                            if (l >= fileSize) break;
                        }
                        System.out.println("文件接收结束");

                        sendFile("fileSuccess");

                        bufferedOutputStream.close();
                        File finalFile = file;
                        Platform.runLater(() -> {
                            ViewModel.getInstance().receivingProgress.set(0);
                            ViewModel.getInstance().receivingProgressPercent.set("0%");
                            ViewModel.getInstance().filePathListHasReceive.add(finalFile.getAbsolutePath());
                            ViewModel.getInstance().filePathOnReceiving.set("无");
                        });
                    }
                }
            } catch (IOException | ClassNotFoundException e) {
                Log.write(e.getMessage());
                throw new RuntimeException(e);
            } finally {
                close();
                Platform.runLater(() -> ViewModel.getInstance().isConnected.set(false));
            }
        }
    }

    public String getHostAddress() {
        InetAddress addr = null;
        try {
            addr = InetAddress.getLocalHost();
            System.out.println(addr);
            return addr.getHostAddress();
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }
        return "null";
    }
}
